# -*- coding: utf-8 -*-
"""
中性策略框架 | 邢不行 | 2024分享会
author: 邢不行
微信: xbx6660
"""
import os
import sys
import pandas as pd
from Functions import *
from joblib import Parallel, delayed, dump
from matplotlib import pyplot as plt
import time
from tqdm import tqdm
import ast
from Config import *

plt.rcParams['figure.figsize'] = [12, 6]
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

# 动态读取config里面配置的strategy_name脚本
Strategy = __import__('strategy.%s' % strategy_name, fromlist=('',))

# 获取当前环境下的python解释器
python_exec = sys.executable


def run(args):
    _select_factor_str = factor_info_to_str(args[0])
    _filter_factor_str = args[1]
    _hold_period = args[2]
    _offset = args[3]
    _select_coin_num_str = args[4]
    _if_use_spot = args[5]
    _result_path = args[6]
    _tmp_path = args[7]
    weight_str = None
    if_save = False
    os.system('%s 2_选币_单offset.py %s %s %s %s %s %s %s %s %s %s' % (python_exec, _select_factor_str, weight_str, _filter_factor_str, _hold_period, _offset, _select_coin_num_str, _if_use_spot, if_save, _result_path, _tmp_path))
    return


if __name__ == '__main__':
    # 运行前需要将因子对应的参数整理好
    # 运行前需要把之前遍历保存下来的数据删掉，或者改个文件名

    # =====遍历回测的准备
    hold_period = '1h'  # 指定hold_period
    offset = 0  # 指定offset
    long_select_coin_num = 5  # 指定多头选币数量
    short_select_coin_num = 'long_nums'  # 指定空头选币数量
    if_use_spot = False  # 是否使用现货
    result_path = os.path.join(back_test_path + '回测结果汇总_历年参数平原.csv')

    # 选币因子的配置
    select_factors = 'BollCountPunish'  # 设置选币因子
    select_params = [24,90,110,120,140,168,180,200,240,320,360,420,480,540,640,720,800,900,1000]  # 设置选币因子的参数。设置前应该保证对应参数数据已经被整理过
    #select_params = [90,110,120,121,122,123,124,125,130,133,140,141,142,143,144,145,146]
    sort_mode = False  # 默认为True

    # 过滤因子的配置
    filter_factors = '涨跌幅max_24'  # 设置过滤因子。默认过滤参数为7。支持过滤因子为空---写法:filter_factors = ''
    keep_same_params = False  # 默认过滤因子参数为7，如果想让过滤因子的参数和选币因子的参数保持一致，设置为True

    # 是否使用共享内存
    is_shared_data = True

    # 判断策略文件中的过滤因子是否为空
    if (not filter_factors) & (len(Strategy.filter_list)):
        print('当设置过滤因子为空时，需要保证引用策略中的过滤因子同样为空，请保证策略中的过滤因子为空！！！')
        exit()

    # 将选币因子和参数组合一起
    select_factor_list = []
    for param in select_params:
        select_factor = select_factors + '_' + str(param)
        select_factor_list.append(select_factor)
    select_factor_list = list(set(select_factor_list))
    factor_para_list = [{f: sort_mode} for f in select_factor_list]

    # 整理过滤因子
    filter_factor_list = []
    if filter_factors:
        if keep_same_params:
            for param in select_params:
                filter_factor = filter_factors.split('_')[0] + '_' + str(param)
                filter_factor_list.append(filter_factor)
        else:
            filter_factor_list.append(filter_factors)
    filter_factor_list = list(set(filter_factor_list))
    all_factor_list = list(set(select_factor_list + filter_factor_list))

    # 合并多空选币数量
    select_coin_num = [long_select_coin_num, short_select_coin_num]
    select_coin_num_str = '+'.join(map(str, select_coin_num))

    # 生成共享内存
    if is_shared_data:
        tmp_path = os.path.join(root_path, 'data/共享内存')
        if not os.path.exists(tmp_path):
            os.makedirs(tmp_path)
        tmp_path = os.path.join(root_path, f'data/共享内存/_tmp_.pkl')
        start_time = time.time()
        print('【遍历】正在生成共享内存...')
        df = read_coin(root_path, hold_period, all_factor_list, if_use_spot, n_jobs, offset)
        dump(df, tmp_path)
        print(f'【遍历】共享内存生成完毕：{time.time() - start_time}')
    else:
        print('不使用共享内存，直接从本地读取数据')
        tmp_path = None

    # 将各个参数组合一下，每个info是遍历一次传入的所有参数
    infos = []
    for select_factor in factor_para_list:
        if filter_factors != '':
            if keep_same_params:
                filter_factor = filter_factors.split('_')[0] + '_' + next(iter(select_factor.keys())).split('_')[1]
            else:
                filter_factor = filter_factors
        else:
            filter_factor = None

        info = [select_factor, filter_factor, hold_period, offset, select_coin_num_str, if_use_spot, result_path, tmp_path]
        infos.append(info)

    # =====并行或串行，依次调用2号脚本
    multiply_process = True  # 是否并行。在测试的时候可以改成False，实际跑的时候改成True
    if multiply_process:
        df_list = Parallel(n_jobs=n_jobs)(delayed(run)(info) for info in tqdm(infos))
    else:
        for info in tqdm(infos):
            run(info)

    # 读取保存的数据
    if not os.path.exists(result_path):
        print(f'参数平原计算统计结果不存在，请检查当前遍历配置信息是否正确 或【{back_test_path}】目录下是否存在文件')
        exit()
    result = pd.read_csv(result_path, encoding='gbk')
    result = result.drop_duplicates().reset_index(drop=True)
    result = result[(result['持仓周期'] == hold_period) & (result['offset'] == offset) & (result['选币数量'] == select_coin_num_str) &
                    (result['是否使用现货'] == if_use_spot)]
    result['参数'] = result['选币因子'].apply(lambda x: int(x.split(',')[2].strip()))
    result['选币因子'] = result['选币因子'].apply(lambda x: x.split("'")[1])
    if '[]' not in result['过滤因子'].values.tolist():
        result['过滤因子'] = result['过滤因子'].apply(lambda x: x.split("'")[1])
    else:
        result['过滤因子'] = ''

    # 转换数据格式
    result['累积净值'] = result['累积净值'].map(lambda x: float(x))
    years = list(range(int(start_date.split('-')[0]), int(end_date.split('-')[0]) + 1, 1))
    result['各年收益'] = result['各年收益'].apply(ast.literal_eval)
    for i, year in enumerate(years):
        result[year] = result['各年收益'].map(lambda x: (x[i]) / 100 + 1)

    # 画历年参数平原图
    fig, axs = plt.subplots(nrows=len(years) + 1, ncols=1)
    if_xticks = True if len(result['参数']) <= 30 else False
    xticks = result['参数'].to_list()
    x_tick_labels = [f'{t}' for t in xticks]
    axs[0].bar(result['参数'], result['累积净值'], width=0.5)
    axs[0].set_title('累计净值')
    if if_xticks:
        axs[0].set_xticks(xticks)
        axs[0].set_xticklabels(x_tick_labels)
    for index, year in enumerate(years):
        axs[index + 1].bar(result['参数'], result[year], width=0.5)
        axs[index + 1].set_title(f'{year}')
        if if_xticks:
            axs[index + 1].set_xticks(xticks)
            axs[index + 1].set_xticklabels(x_tick_labels)
    plt.suptitle('历年参数平原图')
    plt.tight_layout()
    plt.show()
